排查API的connection reset by peer和Timeout exceeded问题 您所在的位置:网站首页 群晖connection reset by peer 排查API的connection reset by peer和Timeout exceeded问题

排查API的connection reset by peer和Timeout exceeded问题

2023-05-02 04:38| 来源: 网络整理| 查看: 265

排查API的connection reset by peer和Timeout exceeded问题 c_rain · · 1337 次点击 · · 开始浏览     这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。 第一次,站长亲自招 Gopher 了>>> 前言

这是一次实际生产中遇到的问题,根据问题模拟反复试验。看完这部分代码,您可以直接了解backlog的工作原理,以及系统调优时该怎么调,不至于胡乱设置。

排查API的connection reset by peer问题:

晚上22点~早上6点会偶尔出现,16:00出现过一次; Client.Timeout exceeded大量出现; 分析问题原因:

connection reset by peer 会有几种情况出现:

处于ESTABLISHED 、 CLOSE_WAIT 、FIN_WAIT1 、FIN_WAIT2 、SYN_RECV或下一个发送序号并不是最后一个队列数据段序号

并且是被动关闭的结束状态的rst状态;

处于TCP_SYN_SENT的rst状态;

Client.Timeout exceeded 会有几种情况出现:

由于rst之后客户端重试时间过短导致的超时; 代码本身业务逻辑超时;- (该项不在本次验证范围) 怀疑对象 nginx 中 keepalive requests 配置 - 排除 内核参数net.core.somaxconn和net.ipv4.tcp_max_syn_backlog; - 排除 内核参数net.core.netdev_max_backlog - 影响不大只会导致丢包,消耗性能 网关nginx本身backlog和应用nginx本身backlog 网络内核参数用途介绍 #当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。这个参数表示该队列的最大值。 net.core.netdev_max_backlog = 262144 #用来限制监听(LISTEN)队列最大数据包的数量,超过这个数量就会导致链接超时或者触发重传机制。 net.core.somaxconn = 262144 #表示那些尚未收到客户端确认信息的连接(SYN消息)队列的长度,可以容纳更多等待连接的网络连接数。 net.ipv4.tcp_max_syn_backlog = 262144

net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog用途1.png根据上图所示net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog用于全连接队列和半连接队列。

半连接队列(Incomplete connection queue),又称 SYN 队列 全连接队列(Completed connection queue),又称 Accept 队列

net.core.netdev_max_backlog用途linux内核代码

int netif_rx(struct sk_buff *skb) { struct softnet_data *queue; unsigned long flags; /* if netpoll wants it, pretend we never saw it */ if (netpoll_rx(skb)) return NET_RX_DROP; if (!skb->tstamp.tv64) net_timestamp(skb); /* * The code is rearranged so that the path is the most * short when CPU is congested, but is still operating. */ local_irq_save(flags); queue = &__get_cpu_var(softnet_data); __get_cpu_var(netdev_rx_stat).total++; if (queue->input_pkt_queue.qlen input_pkt_queue.qlen) { enqueue: dev_hold(skb->dev); __skb_queue_tail(&queue->input_pkt_queue, skb); local_irq_restore(flags); return NET_RX_SUCCESS; //成功 } napi_schedule(&queue->backlog); goto enqueue; } __get_cpu_var(netdev_rx_stat).dropped++; local_irq_restore(flags); kfree_skb(skb); return NET_RX_DROP; //删除包 }

根据代码所示每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的最大数目,一旦超过将被丢弃。网卡阶段包容易导致dropped packet,容易造成丢包现象。

实验内容 模拟网关本身nginx配置 模拟线上sysctl内核参数 ab压测试验 golang编写http.NewRequest长连接请求压测试验 复现步骤

查看内核参数

#systcl -a |grep -e netdev_max_backlog -e somaxconn -e tcp_max_syn_backlog

2.png发现net.core.somaxconn 和net.ipv4.tcp_max_syn_backlog 都不太可能造成backlog。不过netdev_max_backlog很容易造成丢包现象。

查看应用本身backlog

#ss -l

3.jpeg发现nginx本身的backlog是511,然后好奇为什么是这样,就翻阅nginx源码查看。查看nginx源码:

#define NGX_LISTEN_BACKLOG 511

4.jpeg5.jpeg除了Darwin系列系统和freebsd系列系统是由系统本身决定,其余的系统的backlog都是默认511。明显nginx的backlog比较低了。

rst导致的Timeout exceeded问题复现

可以先把nginx backlog调整成10,然后golang的client timeout设置成1golang代码

package main import ( "fmt" "io/ioutil" "net/http" "runtime" "time" ) var httpclient *http.Client func init(){ httpclient = &http.Client{Timeout:1 * time.Second} } func PostForm(url string) (body []byte, err error) { req, err := http.NewRequest("POST", url,nil) if err != nil { fmt.Println("NewRequest: ",err) return nil,err } req.Header.Set("Content-Type","application/x-www-form-urlencoded") resp, err := httpclient.Do(req) if err != nil { fmt.Println("httpclient: ",err) return nil,err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Bad HTTP Response: %v", resp.Status) } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) } func runHttp(dd chan int,i int) { _, err := PostForm("http://192.168.1.107/index.php") if err != nil { fmt.Println("err: ",err) fmt.Println("执行中" ,i) } dd icsk_accept_queue->listen_opt->max_qlen_log; // syn 队列总长度,2^n max_syn_qlen = (1 sk_max_ack_backlog; printf("accept queue length limit: %d\n", max_acc_qlen) print_backtrace(); } }

使用stap 执行上面的代码

# stap -g syn_backlog.c

调试内核代码,捕获80端口的数据包。12.png13.png14.png3425835EE1834051B234D391B78F4B8A.png

经过多次验证得出结论半连接大小不会受到somaxconn和max_syn_backlog影响

somaxconn max_syn_backlog listen backlog 半连接队列大小 65535 65535 10 16 65535 65535 512 1025 65535 65535 65535 65536 65536 65536 65536 131072 181920 181920 181920 262144 在系统参数不修改的情形,盲目调大 listen 的 backlog 对最终半连接队列的大小不会影响。 在 listen 的 backlog 不变的情况下,不要盲目的调大 somaxconn 和 max_syn_backlog 对最终半连接队列的大小不会有影响 三次压测试验 #ab -r -c 200 -t300 http://192.168.1.107/index.php

16.png17.png18.png

经过n次压测可以看到设置nginx默认511时,半连接开关为512。会偶尔出现错误情况,其实压测就200并发。设置backlog 262144时特别稳定。backlog 30时表现的则没那么好。

压测时特意抓了内核参数,发现其实和全队列有实际关系;xiugai.png

加餐部分,net源码解析

## RESET发送流程

图中会调用tcp_reset流程:19.png20.png

tcp_reset函数 net/ipv4/tcp_input.c

static void tcp_reset(struct sock *sk) { /* We want the right error as BSD sees it (and indeed as we do). */ switch (sk->sk_state) { case TCP_SYN_SENT: sk->sk_err = ECONNREFUSED; //报“Connection refused” 错 break; case TCP_CLOSE_WAIT: sk->sk_err = EPIPE; //报“Broken pipe” 错 break; case TCP_CLOSE: return; default: // TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | TCPF_SYN_RECV sk->sk_err = ECONNRESET; //报“Connection reset by peer” 错 } if (!sock_flag(sk, SOCK_DEAD)) sk->sk_error_report(sk); tcp_done(sk); } 1.TCP接收报文:在tcp_v4_rcv,如果校验和有问题,则发送RESET; 2.TCP接收报文:在tcp_v4_rcv,如果 __inet_lookup_skb 函数找不到报文所请求的socket,则发送RESET; 3.TCP收到SYN,发送SYN-ACK,并开始等待连接最后的ACK:在tcp_v4_do_rcv - tcp_v4_hnd_req - tcp_check_req,如果TCP报文头部包含RST,或者包含序列不合法的SYN,则发送RESET; 4.TCP收到连接建立最后的ACK,并建立child套接字后:tcp_v4_do_rcv - tcp_child_process - tcp_rcv_state_process - tcp_ack 函数中,如果发现连接等待的最后ACK序列号有问题: before(ack, prior_snd_una),则发送RESET; 5.TCP在ESTABLISH状态收到报文,在tcp_v4_do_rcv - tcp_rcv_established - tcp_validate_incoming 函数中,如果发现有SYN报文出现在当前的接收窗口中: th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt),则发送RESET; 6.TCP在进行状态迁移时:tcp_rcv_state_process - 如果此时socket处于LISTEN状态,且报文中含有ACK,则发送RESET; 如果此时socket处于FIN_WAIT_1或者FIN_WAIT_2;当接收已经shutdown,并且报文中有新的数据时,发送RESET; 如果测试socket处于FIN_WAIT_1;继续执行

## close时调用tcp_disconnect的Connection reset by peer报错

int tcp_disconnect(struct sock *sk, int flags) { struct inet_sock *inet = inet_sk(sk); struct inet_connection_sock *icsk = inet_csk(sk); struct tcp_sock *tp = tcp_sk(sk); int err = 0; int old_state = sk->sk_state; if (old_state != TCP_CLOSE) tcp_set_state(sk, TCP_CLOSE); /* ABORT function of RFC793 */ if (old_state == TCP_LISTEN) { inet_csk_listen_stop(sk); } else if (tcp_need_reset(old_state) || // 处于ESTABLISHED 、 CLOSE_WAIT 、FIN_WAIT1 、FIN_WAIT2 、SYN_RECV或下一个发送序号并不是最后一个队列数据段序号 并且是被动关闭的结束状态的rst状态; (tp->snd_nxt != tp->write_seq && (1 sk_err = ECONNRESET; } else if (old_state == TCP_SYN_SENT) //处于TCP_SYN_SENT的rst状态 sk->sk_err = ECONNRESET; //...省略 return err; } 查看平常监控及报警

22.jpeg查看监控发现大部分出现connection reset by peer时都处于压测阶段。23.png在根据日常监控可以得知,出现connection reset by peer最小一次并发时大概计算了一下200左右。

总结

1.syn不会受到内核参数影响,只会受到list backlog影响;2.内核参数和list backlog不是随便乱调整的。3.nginx 除了Darwin系列系统和freebsd系列系统是由系统本身决定,其余的系统的backlog都是默认511。明显nginx的backlog比较低了。4.可以通过stap调试内核。

根据如上问题,其实可以看到一个情况。我们可以得知connection reset by peer罪魁祸手其实是内核(struct inet_connection_sock)->icsk_accept_queue->listen_opt->qlen参数导致的,其实是全队列。

整改建议

1.调整内核参数部分:

net.core.somaxconn = 262144 net.ipv4.tcp_max_syn_backlog = 262144 net.core.netdev_max_backlog = 262144

2.调整nginx配置添加

server { listen 80 backlog=262144; }

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:c_rain

查看原文:排查API的connection reset by peer和Timeout exceeded问题

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信 1337 次点击   加入收藏 微博 赞


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有